package internal

import (
	"context"
	"errors"
	"time"

	"github.com/EDDYCJY/go-gin-example/models"
	"github.com/gomodule/redigo/redis"
	"github.com/jinzhu/gorm"
	log "github.com/sirupsen/logrus"
)

var (
	ErrNoMoreGiftBounds  = errors.New("no more gift bounds available for this invite code")
	ErrInvalidCoinsRange = errors.New("invalid coins value is not within the acceptable range")
	ErrEmptyInviteCode   = errors.New("invite code cannot be empty")
)

const (
	InviteCodesCountHash = "invite:codes:count" // 存储每个邀请码的剩余赠送数量
	InviteCodesValueHash = "invite:codes:value" // 存储每个邀请码的赠送积分值
	MaxRewardPoints      = 5000                 // 最大一次允许赠送的积分总量

)

type inviteReward struct {
	UserID     uint64 // User receiving the points
	InviteCode string `json:"invite_code"` // Invitation code
	rewarCoins int    // Actual points rewarded
	conn       redis.Conn
	db         *gorm.DB
}

func NewInviteReward(userID uint64, inviteCode string, conn redis.Conn, db *gorm.DB) *inviteReward {
	return &inviteReward{
		UserID:     userID,
		InviteCode: inviteCode,
		conn:       conn,
		db:         db,
	}
}

// CalculateRewardPoints determines the number of reward points based on the invite code.
func (r *inviteReward) calculateRewardPoints() (res int, err error) {
	if r.InviteCode == "" {
		return 0, ErrEmptyInviteCode
	}
	conn := r.conn
	defer conn.Close()
	defer func() {
		// Return connection to normal state on error.
		if err != nil {
			conn.Do("DISCARD")
		}
	}()

	var (
		remainingCounts int //剩余积分数
		boundsValue     int //积分值
	)

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()

	for {
		select {
		case <-ctx.Done():
			return 0, ctx.Err()
		default:
			// Watch the invite code's remaining bounds.
			if _, err := conn.Do("WATCH", InviteCodesCountHash); err != nil {
				return 0, err
			}
			if err != nil {
				return 0, err
			}

			remainingCounts, err = redis.Int(conn.Do("HGET", InviteCodesCountHash, r.InviteCode))
			if err != nil {
				return 0, err
			}
			boundsValue, err = redis.Int(conn.Do("HGET", InviteCodesValueHash, r.InviteCode)) // 获取邀请码的赠送积分值
			if err != nil {
				return 0, err
			}
			conn.Send("MULTI")
			if remainingCounts > 0 {
				conn.Do("HINCRBY", InviteCodesCountHash, r.InviteCode, -1) // 减少邀请码的剩余赠送数量

			} else {
				return 0, ErrNoMoreGiftBounds
			}

			queued, err := conn.Do("EXEC")
			if err != nil {
				return 0, err
			}
			if queued != nil {

				r.rewarCoins = boundsValue
				return boundsValue, nil
			}
		}
	}

}

// UpdateRewardPoints updates the user's reward points in the database.
// 在执行赠送逻辑前不会检查用户存不存在！不存在，就不赠送了，但是redis记录值仍然会减一
// 调用该函数前，请保证积表中的用户已经存在了
func (r *inviteReward) UpdateRewardPoints() (int, error) {

	if _, err := r.calculateRewardPoints(); err != nil {
		return 0, err
	}

	if r.rewarCoins <= 0 || r.rewarCoins > MaxRewardPoints {
		return 0, ErrInvalidCoinsRange
	}
	err := r.db.Model(&models.Account{}).Where("id = ?", r.UserID).Update("coins", gorm.Expr("coins + ?", r.rewarCoins)).Error
	if err == nil {
		// log.infof("update user coins failed, err: %v", err)
		log.Infof("update user %v coins failed,invited code is:, err: %v", r.UserID, r.InviteCode, err)
	}
	return r.rewarCoins, err

}
